using System;

namespace ValidationFramework.Specifications
{

	/// <summary>
	/// Checks that an object implementing <see cref="IValidatable"/> is valid. If the member doesn't implement <see cref="IValidatable"/> it will check for validity by using a <see cref="PropertyValidationManager"/>.
	/// </summary>
	/// <remarks>
	/// Allow hierarchical validation of objects. 
	/// If <see cref="IValidatable.IsValid"/> returns <c>false</c> then <see cref="Validate"/> will return a <see cref="ValidationResult"/> with <see cref="ValidationResult.ErrorMessage"/> populated with the concatenated value of <see cref="IValidatable.ErrorMessages"/>.
	/// </remarks>
	/// <seealso cref="ValidatableRuleConfigReader"/>
	/// <seealso cref="ValidatableRuleAttribute"/>
#if (!SILVERLIGHT)
	[Serializable]
#endif
	public class IsValidSpec : SpecificationBase
	{
		// --- Fields

		/// <summary>
		/// The concatinated messages from a test are saved here
		/// </summary>
		private string concatinatedErrorMessages;

		// --- Constructors

		public IsValidSpec()
		{
			UseTargetErrorMessages = false;
			TargetRuleSet = "";
		}


		// --- Properties

		/// <summary>
		/// Gets a value indicating wether the DefaultErrorMessage should return the concatinated results of the test or the simple default error message.  
		/// </summary>
		/// <remarks>
		/// If an object is invalid it will often return multiple reasons for its invalidity. Using an aggregate message to describe these errors can make correction paths difficult to see. Use this to denormalize the error messages into a single string so the detailed information isn't lost. 
		/// </remarks>
		public bool UseTargetErrorMessages
		{
			get;
			set;
		}

		/// <summary>
		/// The ruleset to test member against
		/// </summary>
		public string TargetRuleSet { get; set; }


		// --- Methods

		/// <inheritdoc>
		public override string DefaultMessage(string tokenizedMemberName, string descriptorType)
		{
			if (UseTargetErrorMessages)
				return concatinatedErrorMessages;
			else
				return string.Format("The {0} '{1}' is invalid.", descriptorType, tokenizedMemberName);
		}

		/// <inheritdoc>
		public override string ToString()
		{
			return string.Format("The value isn't valid. If the value implmenets IValidatable, its 'IsValid' must be true. If it doesn't implement IValidatable then an attempt to validate via a PropertyValidationManager should return 'true'. See Rules for the Type of this member.");
		}

		/// <inheritdoc>
		public override bool IsSatisfiedBy(object target)
		{
			if (target == null)
				return true;

			// check IsValid property if target implements IValidatable
			var validatable = target as IValidatable;
			if (validatable != null)
			{
				if (validatable.IsValid)
					return true;
				else
				{
					// save the concatinated results for use in GetDefaultErrorMessage
					concatinatedErrorMessages = ResultFormatter.GetConcatenatedErrorMessages(validatable.ErrorMessages);
					return false;
				}
			}

			// attempt to validate using the facade for types that don't implement IValidatable
			var options = new ValidationOptions() { RuleSet = TargetRuleSet };
			var validationResults = Validation.Validate(target, options);
			if (validationResults.Count == 0)
				return true;
			else
				// save the concatinated results for use in GetDefaultErrorMessage
				concatinatedErrorMessages = ResultFormatter.GetConcatenatedErrorMessages(validationResults);
				return false;
		}

		//public override bool CheckType(Type typeToTest)
		//{
		//    return typeof(IValidatable).IsAssignableFrom(typeToTest);
		//}

		public override bool IsEquivalentTo(ISpecification spec)
		{
			var ivSpecification = (IsValidSpec)spec;

			return ivSpecification.TargetRuleSet == TargetRuleSet 
				&& ivSpecification.UseTargetErrorMessages == UseTargetErrorMessages;
		}
	}
}